
/* GCSx
** GUI.CPP
**
** Base gui classes and code
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

SDLKey Desktop::keypadConvert[10] = {
	SDLK_INSERT,
	SDLK_END,
	SDLK_DOWN,
	SDLK_PAGEDOWN,
	SDLK_LEFT,
	SDLK_KP5,
	SDLK_RIGHT,
	SDLK_HOME,
	SDLK_UP,
	SDLK_PAGEUP,
};
	
int Window::nextId = 1;

Desktop* desktop = NULL;

WindowParent::~WindowParent() { start_func
}

WindowCollection::WindowCollection() : windowDataDisplay(), windowDataFocus() { start_func
    handleEvents = NULL;

    currentFocus = NULL;
    previousFocus = NULL;
    lastFocus = NULL;
    currentMouseFocus = NULL;
    lastMouseX = -1;
    lastMouseY = -1;

    modalPos = NULL;
    allowDesktopClick = 0;
    
    overallWidth = 0;
    overallHeight = 0;
    canvasX = 0;
    canvasY = 0;
    canvasWidth = 0;
    canvasHeight = 0;
    
    updateRect.w = 0;
}

WindowCollection::~WindowCollection() { start_func
}

void WindowCollection::addToUpdateRect(const Rect& add) { start_func
    boundRects(updateRect, add);
}

void WindowCollection::setEventHandler(int (*eventHandler)(const SDL_Event* event)) { start_func
    handleEvents = eventHandler;
}

// (verify that a window is still part of the collection)
int WindowCollection::verifyWindow(const Window* target) const { start_func
    if (target == NULL) return 0;

    list<Window*>::const_iterator end = windowDataDisplay.end();
    for (list<Window*>::const_iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
        assert(*pos);
        if (*pos == target) return 1;
    }
    return 0;
}

Window* WindowCollection::verifyWindow(int targetId) { start_func
    if (targetId == 0) return NULL;

    list<Window*>::iterator end = windowDataDisplay.end();
    for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
        assert(*pos);
        if ((*pos)->getId() == targetId) return *pos;
    }
    return NULL;
}

// Run an event
// (after running an SDL_CLOSE event, any window pointers other than 'target' we have can't be trusted)
int WindowCollection::windowRunEvent(Window* target, const SDL_Event* event) { start_func
    assert(target);
    assert(event);

    return target->event(currentFocus == target, event);
}

// (internal- find top window by coordinates)
const Window* WindowCollection::findWindowByCoords(int x, int y) const { start_func
    list<Window*>::const_reverse_iterator end = windowDataDisplay.rend();
    for (list<Window*>::const_reverse_iterator pos = windowDataDisplay.rbegin(); pos != end; ++pos) {
        assert(*pos);
        if ((*pos)->isCoords(x, y)) return *pos;
    }
    return NULL;
}
Window* WindowCollection::findWindowByCoords(int x, int y) { start_func
    list<Window*>::reverse_iterator end = windowDataDisplay.rend();
    for (list<Window*>::reverse_iterator pos = windowDataDisplay.rbegin(); pos != end; ++pos) {
        assert(*pos);
        if ((*pos)->isCoords(x, y)) return *pos;
    }
    return NULL;
}

// (internal- tell new window it has mouse focus, if applicable; revert to normal mouse pointer)
Window* WindowCollection::recheckMouseFocus(int sendMoveEvent) { start_func
    Window* window = findWindowByCoords(lastMouseX, lastMouseY);
    
    // Refusing?
    if (window) {
        if (window->refuseAll()) window = NULL;
    }
    
    // Disallow mouse focus on anything but the current modal window OR a window
    // sorted above it (if not on modal window, go to NULL)
    if (modalPos) {
        if (!window) {
            window = NULL;
        }
        else if ((window->windowSort() <= Window::WINDOWSORT_MODAL) && (window != modalPos)) {
            window = NULL;
        }
    }
    
    if (window != currentMouseFocus) {
        desktop->focusEvent(SDL_MOUSEFOCUS, 0, currentMouseFocus);
        desktop->focusEvent(SDL_MOUSEFOCUS, 1, window);
        currentMouseFocus = window;
        selectMouse(MOUSE_NORMAL);
        if (sendMoveEvent) {
            SDL_Event customEvent;
            customEvent.type = SDL_MOUSEMOTION;
            customEvent.motion.x = lastMouseX;
            customEvent.motion.y = lastMouseY;
            customEvent.motion.state = SDL_GetMouseState(NULL, NULL);
            customEvent.motion.xrel = 0;
            customEvent.motion.yrel = 0;
            insertEvent(&customEvent);
        }
    }
    
    return window;
}

// (internal- tell new window it has input focus)
int WindowCollection::changeInputFocus(Window* window, int toTop) { start_func
    if (window != currentFocus) {
        // Refusing?
        if (window) {
            if (window->refuseAll()) return 0;
        }

        if ((window) && (toTop)) {
            // Bring to top of focus stack first
            bringToTop(window);
            
            // bringToTop called us, so we're done
            return 1;
        }
        
        // Always announce desktop losing focus
        if ((!currentFocus) && (allowDesktopClick) && (this == desktop)) desktop->broadcastEvent(SDL_SPECIAL, SDL_DESKTOPINACTIVE);
       
        // Possibilities-
        // tempFocus to tempFocus- normal focus change
        // normal/blank to normal/blank- normal focus change, kill previousfocus
        
        // normal/blank to tempFocus- old focus doesn't "lose" it
        // tempFocus to normal/blank- both temp and previous focus lose it, even if changing
        //                            back to the previous, it still must lose it temp.
        int from = 0;
        int to = 0;
        if (currentFocus) {
            if (currentFocus->tempFocus()) from = 1;
        }
        if (window) {
            if (window->tempFocus()) to = 1;
        }
    
        if (from == to) {
            desktop->focusEvent(SDL_INPUTFOCUS, 0, currentFocus);
            // From normal to normal, if we STILL have a previous focus...
            if (!to) {
                // Kill it. If it's the new focus window, tho, don't send an event.
                if ((previousFocus != window) && (previousFocus)) desktop->focusEvent(SDL_INPUTFOCUS, 0, previousFocus);
                previousFocus = NULL;
            }
        }
        else if (from == 0) {
            previousFocus = currentFocus;
        }
        else {
            desktop->focusEvent(SDL_INPUTFOCUS, 0, previousFocus);
            desktop->focusEvent(SDL_INPUTFOCUS, 0, currentFocus);
            previousFocus = NULL;
        }

        desktop->focusEvent(SDL_INPUTFOCUS, 1, window);
        currentFocus = window;

        // Always announce desktop gaining focus
        if ((!currentFocus) && (allowDesktopClick) && (this == desktop)) desktop->broadcastEvent(SDL_SPECIAL, SDL_DESKTOPACTIVE);
        return 1;
    }
    return 0;
}

void WindowCollection::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    list<Window*>::iterator end = windowDataDisplay.end();
    for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
        assert(*pos);
        (*pos)->resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
    }
    
    // Call AFTER resizing windows, as the docked ones have now rearranged themselves
    adjustCanvas();
}

// (internal- adjust our canvas size/pos to match any currently docked windows)
void WindowCollection::adjustCanvas() { start_func
    // (skip if we aren't using this feature)
    if ((overallWidth) && (overallHeight)) {
        canvasX = 0;
        canvasY = 0;
        int canvasRight = overallWidth;
        int canvasBottom = overallHeight;
        
        // Scan all windows for docked ones
        list<Window*>::iterator end = windowDataDisplay.end();
        for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
            Window* wpos = *pos;
            assert(wpos);
            if (wpos->isDocked()) {
                // Determine what edge it is docked at based on what screen edges it touches
                // If a window touches two opposite edges, neither is the docking edge
                // (for example, menubar touches left/right but is docked at the top)
                if ((wpos->getX() <= 0) && (wpos->getX() + wpos->getWidth() < overallWidth)) {
                    // Left edge- increase canvasX if needed
                    int size = wpos->getWidth() + wpos->getX();
                    if (size > canvasX) canvasX = size;
                }
                if ((wpos->getX() + wpos->getWidth() >= overallWidth) && (wpos->getX() > 0)) {
                    // Right edge- decrease canvasRight if needed
                    if (wpos->getX() < canvasRight) canvasRight = wpos->getX();
                }
                if ((wpos->getY() <= 0) && (wpos->getY() + wpos->getHeight() < overallHeight)) {
                    // Top edge- increase canvasY if needed
                    int size = wpos->getHeight() + wpos->getY();
                    if (size > canvasY) canvasY = size;
                }
                if ((wpos->getY() + wpos->getHeight() >= overallHeight) && (wpos->getY() > 0)) {
                    // Bottom edge- decrease canvasBottom if needed
                    if (wpos->getY() < canvasBottom) canvasBottom = wpos->getY();
                }
            }
        }        

        canvasWidth = canvasRight - canvasX; 
        canvasHeight = canvasBottom - canvasY;

        // Here, we "force" any frame windows to be within our confines
        for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
            Window* wpos = *pos;
            assert(wpos);
            if (typeid(*wpos) == typeid(FrameWindow)) {
                dynamic_cast<FrameWindow*>(wpos)->redoSize();
            }
        }
    }
}

void Window::move(int xPos, int yPos) { start_func
    int prevX = x;
    int prevY = y;
    x = xPos;
    y = yPos;
    // @TODO: At some point we might be able to scroll data and just redraw
    // partial or not at all
    setDirty(1);
    if (parent) parent->childMoved(prevX, prevY, x, y, this);
}

void Window::resize(int newWidth, int newHeight, int newViewWidth, int newViewHeight, int fromParent) { start_func
    int updateParent = 0;
    int oldWidth = width;
    int oldHeight = height;

    if (newViewWidth < 0) newViewWidth = viewWidth;
    if (newViewHeight < 0) newViewHeight = viewHeight;
    
    // Compare so we don't resize if not needed and to prevent unneeded recursion                             
    if ((newWidth != width) || (newHeight != height) ||
        (newViewWidth != viewWidth) || (newViewHeight != viewHeight)) {

        if ((newWidth != width) || (newHeight != height)) updateParent = 1;
        
        viewWidth = newViewWidth;
        viewHeight = newViewHeight;
        width = newWidth;
        height = newHeight;

        setDirty(1);
        
        // Update parent, including FrameWindow or WidgetScroll if there is one
        if ((parent) && (!fromParent) && (updateParent)) {
            parent->childResized(oldWidth, oldHeight, width, height, this);
            if (typeid(*parent) == typeid(FrameWindow)) {
                dynamic_cast<FrameWindow*>(parent)->updateClientSize();
            }
            else if (typeid(*parent) == typeid(WidgetScroll)) {
                dynamic_cast<WidgetScroll*>(parent)->updateClientSize();
            }
        }
    }
    
    if ((width > 0) && (height > 0) && (parent) && (viewWidth != 0) && (viewHeight != 0)) visible = 1;
    else visible = 0;
}

int Window::attemptClose() { start_func
    return 1;
}

/* Not currently used or supported
int Window::usesAlpha() const { start_func
    return 0;
}
*/

int Window::isDocked() const { start_func
    return 0;
}

int Window::tempFocus() const { start_func
    return 0;
}

int Window::refuseAll() const { start_func
    return 0;
}

Window::CommandSupport Window::supportsCommand(int code) const { start_func
    return COMMAND_HIDE;
}

Window::WindowType Window::windowType() const { start_func
    return WINDOW_UNKNOWN;
}

int Window::wantsToBeDeleted() const { start_func
    return 1;
}

Window::WindowSort Window::windowSort() const { start_func
    return WINDOWSORT_NORMAL;
}

int Window::isOnDesktop() const { start_func
    if (parent) {
        if (parent == desktop) return desktop->verifyWindow(this);
        else return dynamic_cast<Window*>(parent)->isOnDesktop();
    }
    return 0;
}
    
void Window::closeWindow(int skipAttempt) { start_func
    if (isOnDesktop()) {
        if ((parent) && (parent != desktop)) {
            dynamic_cast<FrameWindow*>(parent)->closeWindow(skipAttempt);
        }
        else {
            if (!skipAttempt) {
                if (!attemptClose()) return;
            }
            desktop->focusEvent(SDL_CLOSE, 0, this);
        }
    }
}

const char* Window::tooltip(int xPos, int yPos) const { start_func
    return NULL;
}

int Window::isCoords(int xPos, int yPos) const { start_func
    if ((xPos >= x) && (yPos >= y) && (xPos < x + width) && (yPos < y + height)) return 1;
    return 0;
}

int Window::getId() const { start_func
    return id;
}

int Window::getX() const { start_func
    return x;
}

int Window::getY() const { start_func
    return y;
}

int Window::getScreenX() const { start_func
    if (parent) return parent->getScreenX() + x;
    return x;
}

int Window::getScreenY() const { start_func
    if (parent) return parent->getScreenY() + y;
    return y;
}

int Window::getWidth() const { start_func
    return width;
}

int Window::getHeight() const { start_func
    return height;
}

void Window::getRect(Rect& rect) const { start_func
    rect.x = x;
    rect.y = y;
    rect.w = width;
    rect.h = height;
}

void Window::undoNotify(int undoType, int undoItemId, int undoItemSubId, int uX, int uY, int uW, int uH) { start_func
}

Window::Window() { start_func
    parent = NULL;
    parentNotify = NULL;
    visible = 0;
    id = nextId++;
    
    width = height = -1;
    viewWidth = viewHeight = -1;
    
    dirty = 1;
    totalDirty = 1;
    childDirty = 0;
}

Window::~Window() { start_func
    if (parent) parent->childDeleted(this);
    if ((parentNotify) && (parentNotify != parent)) parentNotify->childDeleted(this);
    cleanEvents(this);
}

#ifndef NDEBUG
const char* Window::debugDump() const { start_func
    return "(untyped window)";
}
#endif

void Window::setDirty(int total) { start_func
    if (total) totalDirty = 1;
    if (!dirty) {
        dirty = 1;
        if (parent) parent->setChildDirty();
    }
}

void Window::childMoved(int fromX, int fromY, int toX, int toY, Window* child) { start_func
    assert(child);
}

void Window::childResized(int fromW, int fromH, int toW, int toH, Window* child) { start_func
    assert(child);
}

void Window::childDeleted(Window* child) { start_func
    assert(child);
}

void Window::childModified(Window* child) { start_func
    assert(child);
    if (parent) parent->childModified(child);
}

void Window::siblingModified(Window* sibling) { start_func
    assert(sibling);
}

void Window::setChildDirty() { start_func
    childDirty = 1;
    if (parent) parent->setChildDirty();
}

int Window::isDirty() const { start_func
    // Don't check totalDirty, it's never set if dirty isn't set
    return dirty || childDirty;
}

void Window::setParent(WindowParent* wParent) { start_func
    parent = wParent;

    if ((width > 0) && (height > 0) && (parent) && (viewWidth != 0) && (viewHeight != 0)) visible = 1;
    else visible = 0;

    if ((dirty) && (parent)) parent->setChildDirty();
}

void Window::setParentNotify(WindowParent* wParentNotify) { start_func
    parentNotify = wParentNotify;
}

void Window::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    // Default does nothing
}

void WindowCollection::addWindow(Window* item, int giveFocus) { start_func
    assert(item);
    
    // Abort if already part of our collection
    if (verifyWindow(item)) return;

    Window::WindowSort sortType = item->windowSort();

    // Scan through windows until we find one of equal/lower sort value
    // (can't use reverse iterator)
    list<Window*>::iterator pos = windowDataDisplay.end();
    list<Window*>::iterator begin = windowDataDisplay.begin();
    while (pos != begin) {
        --pos;
        assert(*pos);
        if ((*pos)->windowSort() <= sortType) {
            // Insert after this one
            ++pos; // Move one element back towards the end of the list
            windowDataDisplay.insert(pos, item);
            break;
        }
    }
    
    if (pos == windowDataDisplay.begin()) {
        // Insert at beginning
        windowDataDisplay.push_front(item);
    }
    
    // Add to focus stack at beginning or end
    if ((giveFocus) || (sortType == Window::WINDOWSORT_MODAL)) {
        windowDataFocus.push_back(item);
    }
    else {
        windowDataFocus.push_front(item);
    }

    item->setParent(this);

    Rect toUpdate;
    item->getRect(toUpdate);
    addToUpdateRect(toUpdate);
    if (item->isDocked()) adjustCanvas();
    recheckMouseFocus(1);
    if ((giveFocus) || (sortType == Window::WINDOWSORT_MODAL)) changeInputFocus(item);
    if (sortType == Window::WINDOWSORT_MODAL) modalPos = item;
}

void WindowCollection::removeWindow(Window* item) { start_func
    assert(item);

    Window* highestModal = NULL;
    
    // Main list of windows
    // (can't use reverse iterators)
    // Note that we purposely use windowDataDisplay here
    // and windowDataFocus in deleteAllWindows
    list<Window*>::iterator pos = windowDataDisplay.end();
    list<Window*>::iterator found = windowDataDisplay.end();
    list<Window*>::iterator begin = windowDataDisplay.begin();
    while (pos != begin) {
        --pos;
        assert(*pos);
        if (*pos == item) {
            found = pos;
        }
        else if (!highestModal) {
            if ((*pos)->windowSort() == Window::WINDOWSORT_MODAL) highestModal = *pos;
        }
    }
    
    if (found != windowDataDisplay.end()) {
        // Found- remove
        item->setParent(NULL);
        if (previousFocus == item) previousFocus = NULL;
        Rect toUpdate;
        item->getRect(toUpdate);
        addToUpdateRect(toUpdate);
        windowDataDisplay.erase(found);
        cleanEvents(item);
        
        // Also remove from focus stack
        for (pos = windowDataFocus.begin(); pos != windowDataFocus.end(); ++pos) {
            assert(*pos);
            if (*pos == item) {
                windowDataFocus.erase(pos);
                break;
            }
        }
        
        if (item->isDocked()) adjustCanvas();
        
        if (currentMouseFocus == item) {
            currentMouseFocus = NULL;
            recheckMouseFocus(1);
        }

        if (modalPos == item) {
            modalPos = highestModal;
        }

        if (currentFocus == item) {
            // (remove before swapping, to prevent it from showing up in previousFocus)
            currentFocus = NULL;
            if (previousFocus) changeInputFocus(previousFocus);
            else if (!windowDataFocus.empty()) changeInputFocus(*(--windowDataFocus.end()));
            else changeInputFocus(NULL);
        }
    }    
    
    assert(item != currentFocus);
    assert(item != previousFocus);
    assert(item != currentMouseFocus);
}

// pos = 0 for first, 1 for second, etc.
Window* WindowCollection::findWindow(Window::WindowType type, int pos) { start_func
    assert(pos >= 0);
    assert(type > Window::WINDOW_UNKNOWN);
    assert(type <= Window::WINDOW_LASTTYPE);

    // This function by definition searches focus stack (see header)
    list<Window*>::iterator iend = windowDataFocus.end();
    for (list<Window*>::iterator ipos = windowDataFocus.begin(); ipos != iend; ++ipos) {
        assert(*ipos);
        if ((*ipos)->windowType() == type) {
            if (pos == 0) return *ipos;
            --pos;
        }
    }

    return NULL;
}

Window* WindowCollection::findFocusWindow() { start_func
    return currentFocus;
}

Window* WindowCollection::findPreviousFocusWindow() { start_func
    if (previousFocus) return previousFocus;
    return currentFocus;
}

void WindowCollection::bringToTop(Window* item, int giveFocus) { start_func
    if (!item) {
        if ((allowDesktopClick) && (!modalPos)) changeInputFocus(NULL, 0);
        return;
    }
    
    int found = 0;
    
    // First, find and remove...
    list<Window*>::iterator end = windowDataDisplay.end();
    for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
        assert(*pos);
        if (*pos == item) {
            windowDataDisplay.erase(pos);
            found = 1;
            break;
        }
    }
    
    // Then, insert
    if (found) {
        int sortType = item->windowSort();

        // (can't use reverse iterator)
        list<Window*>::iterator pos = windowDataDisplay.end();
        list<Window*>::iterator begin = windowDataDisplay.begin();
        while (pos != begin) {
            --pos;
            assert(*pos);
            if ((*pos)->windowSort() <= sortType) {
                // Insert after this one
                ++pos; // Move one element back towards the end of the list
                windowDataDisplay.insert(pos, item);
                break;
            }
        }

        if (pos == windowDataDisplay.begin()) {
            // Insert at beginning
            windowDataDisplay.push_front(item);
        }
        
        // Find and move in focus stack too?
        if (giveFocus) {
            for (pos = windowDataFocus.begin(); pos != windowDataFocus.end(); ++pos) {
                assert(*pos);
                if (*pos == item) {
                    windowDataFocus.erase(pos);
                    windowDataFocus.push_back(item);
                    break;
                }
            }
            
            changeInputFocus(item, 0); // Don't recurse to bringToTop again
        }

        Rect toUpdate;
        item->getRect(toUpdate);
        addToUpdateRect(toUpdate);
        recheckMouseFocus(1);
    }
}

void WindowCollection::sendToBottom(Window* item) { start_func
    assert(item);

    int found = 0;
    
    // First, find and remove...
    list<Window*>::iterator end = windowDataDisplay.end();
    for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
        assert(*pos);
        if (*pos == item) {
            windowDataDisplay.erase(pos);
            found = 1;
            break;
        }
    }
    
    // Then, insert
    if (found) {
        Window::WindowSort sortType = item->windowSort();

        list<Window*>::iterator end = windowDataDisplay.end();
        list<Window*>::iterator pos;
        for (pos = windowDataDisplay.begin(); pos != end; ++pos) {
            assert(*pos);
            if ((*pos)->windowSort() >= sortType) {
                // Insert before this one
                windowDataDisplay.insert(pos, item);
                break;
            }
        }

        if (pos == windowDataDisplay.end()) {
            // Insert at end
            windowDataDisplay.push_back(item);
        }
        
        // Now, find in focus stack, move to bottom
        end = windowDataFocus.end();
        for (pos = windowDataFocus.begin(); pos != end; ++pos) {
            assert(*pos);
            if (*pos == item) {
                windowDataFocus.erase(pos);
                windowDataFocus.push_front(item);
                break;
            }
        }

        if (currentFocus == item) {
            if (previousFocus) changeInputFocus(previousFocus);
            else changeInputFocus(*(--windowDataFocus.end()));
        }

        Rect toUpdate;
        item->getRect(toUpdate);
        addToUpdateRect(toUpdate);
    }
}

int WindowCollection::handleEvent(const SDL_Event* event) { start_func
    Window* window = NULL;
    Window* exempt = NULL;
    int broadcast = 0;
    
    assert(event);
    SDL_Event eventCopy = *event;

    // Select appropriate window
    switch (eventCopy.type) {
        case SDL_NOEVENT:
            return 0;
    
        case SDL_QUIT:
        case SDL_SYSKEY:
        case SDL_SPECIAL:
        case SDL_OBJECTCHANGE:
            // (exempt window may exist)
            exempt = (Window*)eventCopy.user.data2;
        case SDL_ACTIVEEVENT:
        case SDL_VIDEORESIZE:
        case SDL_VIDEOEXPOSE:
            broadcast = 1;
            break;
            
        case SDL_CLOSE:
        case SDL_INPUTFOCUS:
        case SDL_MOUSEFOCUS:
            // Window is defined as part of the event
            window = (Window*)eventCopy.user.data1;
            // (global SDL_CLOSE events not currently used, but can be supported)
            // if (window == NULL) broadcast = 1;
            // This will catch it if it recurses/loops back to us by mistake
            assert(window);
            eventCopy.user.data1 = NULL;
            break;

        case SDL_COMMAND:
        case SDL_KEYDOWN:
        case SDL_KEYUP:
            window = currentFocus;
            if (!window) {
                // @TODO: don't break if we ever start broadcasting keyups/commandreleases
                if (eventCopy.type == SDL_KEYUP) break;
                if (eventCopy.type == SDL_KEYDOWN) eventCopy.type = SDL_SYSKEY;
                broadcast = 1;
            }
            break;

        case SDL_MOUSEMOTION:
            lastMouseX = eventCopy.motion.x;
            lastMouseY = eventCopy.motion.y;

            // If a button is pressed, we keep the previous focus indefinitely
            if ((eventCopy.motion.state & SDL_BUTTON_LMASK) || (eventCopy.motion.state & SDL_BUTTON_RMASK)) {
                window = currentMouseFocus;
            }
            else {
                window = recheckMouseFocus();
            }
            
            // Adjust coordinates to 0-based for window
            if (window) {
                eventCopy.motion.x -= window->getX();
                eventCopy.motion.y -= window->getY();
            }
            break;

        case SDL_MOUSEBUTTONUP:
            // Mouse release also retains previous window focus
            lastMouseX = eventCopy.button.x;
            lastMouseY = eventCopy.button.y;
            window = currentMouseFocus;

            // Adjust coordinates to 0-based for window
            if (window) {
                eventCopy.button.x -= window->getX();
                eventCopy.button.y -= window->getY();
            }
            break;

        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            lastMouseX = eventCopy.button.x;
            lastMouseY = eventCopy.button.y;
            window = recheckMouseFocus();

            // Adjust coordinates to 0-based for window
            if ((window) || ((allowDesktopClick) && (!modalPos))) {
                // No focus on wheels
                if ((event->button.button != SDL_BUTTON_WHEELUP) &&
                    (event->button.button != SDL_BUTTON_WHEELDOWN)) {
                    changeInputFocus(window);
                }
                if (window) {
                    eventCopy.button.x -= window->getX();
                    eventCopy.button.y -= window->getY();
                }
            }
            else if (currentFocus) {
                // NULL focus will take away focus from a tempfocus window (popup)
                if ((currentFocus->tempFocus()) &&
                    (event->button.button != SDL_BUTTON_WHEELUP) &&
                    (event->button.button != SDL_BUTTON_WHEELDOWN)) {
                    if (previousFocus) changeInputFocus(previousFocus);
                    else changeInputFocus(NULL);
                }
            }
            break;
            
        default:
            return 0;
    }

    // Propogate event
    if (window) {
        // Don't recurse (never broadcast in this case, either)
        if ((void*)window == (void*)this) return 0;
    
        // Refusing?
        if (!window->refuseAll()) {
            // single window event, is allowed to close
            if (windowRunEvent(window, &eventCopy)) {
                if ((eventCopy.type == SDL_CLOSE) && (window->wantsToBeDeleted())) {
                    delete window;
                }
                return 1;
            }
        }
        
        // If window is a floating panel of another window, attempt key/cmd there
        if ((typeid(*window) == typeid(FrameWindow)) && (!modalPos)) {
            if (dynamic_cast<FrameWindow*>(window)->getFramePanel()) {
                if ((eventCopy.type == SDL_KEYDOWN) || (eventCopy.type == SDL_KEYUP) || (eventCopy.type == SDL_COMMAND)) {
                    if (windowRunEvent(dynamic_cast<FrameWindow*>(window)->getFramePanel(), &eventCopy)) return 1;
                }
            }
        }

        // Keypresses now broadcast unless modal window open
        if ((eventCopy.type == SDL_KEYDOWN) && (!modalPos)) {
            eventCopy.type = SDL_SYSKEY;
            broadcast = 1;
        }
        // If modal window, run as shortcut
        else if ((eventCopy.type == SDL_KEYDOWN) && (this == desktop)) {
            int count = config->readShortcut(eventCopy.key.keysym.sym, eventCopy.key.keysym.mod, 0);
            if (count) {
                // Recurse through all potential shortcuts
                for (int pos = 1; pos <= count; ++pos) {
                    int cmd = config->readShortcut(eventCopy.key.keysym.sym, eventCopy.key.keysym.mod, pos);
                    // Stop when one works
                    if (desktop->broadcastEvent(SDL_COMMAND, cmd, NULL, NULL, 1)) {
                        desktop->lastShortcutKey = eventCopy.key.keysym.sym;
                        return 1;
                    }
                }
            }
            return 0;
        }
        // SDL_COMMAND goes to global event unless modal window open
        else if ((handleEvents) && (eventCopy.type == SDL_COMMAND) && (!modalPos) && (this == desktop)) {
            return handleEvents(&eventCopy);
        }
        // (anything else is just plain done)
        else return 0;
    }
    
    if (broadcast) {
        int result = 0;

        list<Window*>::reverse_iterator pos;
        list<Window*>::reverse_iterator next;
        list<Window*>::reverse_iterator end = windowDataDisplay.rend();
    
        // Reverse order, because if a window moves, it should be towards the end
        for (pos = windowDataDisplay.rbegin(); pos != end; pos = next) {
            assert(*pos);
            if ((eventCopy.type == SDL_SPECIAL) && (eventCopy.user.code == SDL_PREQUIT) && (desktop->cancelQuitState())) break;
            
            // Note the next element in sequence now, before we run event
            next = pos;
            ++next;
            
            // Don't recurse
            if ((void*)(*pos) == (void*)this) return 0;

            // Refusing?
            if ((*pos)->refuseAll()) continue;
            
            // Exempt?
            if (*pos == exempt) continue;

            // Run event- if window closes or moves to top, we already have next item
            if (windowRunEvent(*pos, &eventCopy)) result = 1;
            
            // If a SDL_SYSKEY event that was used, we're done
            if ((eventCopy.type == SDL_SYSKEY) && (result)) break;
        }
        
        // Unused SDL_SYSKEY events attempts to run as shortcut now
        if ((eventCopy.type == SDL_SYSKEY) && (!result) && (this == desktop)) {
            int count = config->readShortcut(eventCopy.key.keysym.sym, eventCopy.key.keysym.mod, 0);
            if (count) {
                // Close all popup menus at any level
                PopupMenu* menu = dynamic_cast<PopupMenu*>(desktop->findWindow(Window::WINDOW_MENUBAR, 0));
                if (menu) menu->closeAllSubmenus(0);
                // Recurse through all potential shortcuts
                for (int pos = 1; pos <= count; ++pos) {
                    int cmd = config->readShortcut(eventCopy.key.keysym.sym, eventCopy.key.keysym.mod, pos);
                    // Stop when one works
                    if (desktop->broadcastEvent(SDL_COMMAND, cmd, NULL, NULL, 1)) {
                        desktop->lastShortcutKey = eventCopy.key.keysym.sym;
                        result = 1;
                        break;
                    }
                }
            }
        }
        
        // Goes to event handler, unless-
        //   SDL_COMMAND or SDL_SYSKEY that was used, or
        //   SDL_CLOSE
        //   (global SDL_CLOSE events not currently used, but can be supported)
        if ((handleEvents) &&
            // (eventCopy.type != SDL_CLOSE) &&
            (((eventCopy.type != SDL_COMMAND) && (eventCopy.type != SDL_SYSKEY)) || (!result))) {
            
            if (handleEvents(&eventCopy)) result = 1;
        }
        
        return result;
    }

    return 0;
}

void Desktop::setGameLoop(void (*gameLoop)(int)) { start_func
    gameLoopH = gameLoop;
    allowDesktopClick = gameLoop ? 1 : 0;
}

void Desktop::setSupportsHandler(Window::CommandSupport (*supportsHandler)(int code)) { start_func
    supportsCommandH = supportsHandler;
}

Window::CommandSupport Desktop::supportsCommand(int code, Window::CommandSupport prior) const { start_func
    if (supportsCommandH) {
        Window::CommandSupport sc = supportsCommandH(code);
        if ((prior == Window::COMMAND_HIDE) || (sc & Window::COMMAND_ENABLE)) return sc;
    }
    return prior;
}

void Desktop::preventDoubleClick() { start_func
    dclickTicks = 0;
}

void Desktop::updateScreen() { start_func  
    // Restore mouse (screen should have NULL clip right now)
    unblitMouse();
    
    // Screen clip
    Rect screenClip = { 0, 0, overallWidth, overallHeight };

    // Start with updateRect at it's current status
    if (updateRect.w) {
        // Ensure clipped to screen size
        intersectRects(updateRect, screenClip);

        // Fill with background (screen should have NULL clip right now)
        SDL_FillRect(getScreen(), &updateRect, convertRGB(guiRGB[backgroundColorIndex], backgroundAlpha));
        if (backgroundImage) {
            Rect imageClip = { bkDestX, bkDestY, bkSrcW, bkSrcH };
            if (intersectRects(imageClip, updateRect)) {
                blit(bkSrcX + imageClip.x - bkDestX, bkSrcY + imageClip.y - bkDestY,
                     backgroundImage, imageClip.x, imageClip.y, getScreen(), imageClip.w, imageClip.h);
            }
        }
    }
    
    SDL_Surface* screen = getScreen();
    if ((childDirty) || (updateRect.w)) {
        list<Window*>::iterator end = windowDataDisplay.end();
        for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
            assert(*pos);
            // Start with clip area of entire window
            Rect clipDirty;
            (*pos)->getRect(clipDirty);
            // Actual clip to within screen size
            Rect clipTotal = clipDirty;
            // If this intersects with our current update area OR window is dirty
            if ((intersectRects(clipDirty, updateRect)) || ((*pos)->isDirty())) {
                // ...then display window (we only request area that matches our update)
                (*pos)->display(screen, clipDirty, clipTotal, 0, 0);
                // and add in anything it did update to our update area
                boundRects(updateRect, clipDirty);
            }
        }
    }
    
    childDirty = 0;

    SDL_SetClipRect(screen, (SDL_Rect*)NULL);
    blitMouse();
    intersectRects(updateRect, screenClip);
    
    // Call game loop, if any
    if (gameLoopH) {
        // If desktop can be clicked AND is active, pass keys to game
        gameLoopH((allowDesktopClick) && (currentFocus == NULL));
    }
    // Update our screen now
    screenFlip(&updateRect);
    postGLScreenFlip();
    
    // Clear updateRect for next time
    updateRect.w = 0;
}

int Desktop::eventLoop() { start_func
    Window* modalLoopPos = NULL;
    SDL_Event event;
    SDL_Event customEvent;
    customEvent.type = SDL_SPECIAL;
    customEvent.user.data1 = NULL;
    customEvent.user.data2 = NULL;
    
    // A modal loop? Remember what window is "ours"
    if (modalPos) modalLoopPos = modalPos;

    modalReturn = 0;
    ++eventLoopCount;

    while (!quit) {
        if (grabEvent(&event)) {
            int gameMode = (gameLoopH) && (allowDesktopClick) && (currentFocus == NULL);
            int storeToGame = 0;

            idleTimeout = 0;
            didShortTimeout = 0;
            
            // Tooltip? Remove
            if ((tooltip) && (event.type != SDL_MOUSEFOCUS)) {
                removeWindow(tooltip);
                delete tooltip;
                tooltip = NULL;
                tooltipMs = 0;
            }
            
            // Skip user-generated input events EXCEPT key ups/button ups
            // (to avoid stuck keys)
            // Mouse movement is OK too
            if (eventCleanup) {
                if ((event.type == SDL_KEYDOWN) ||
                    (event.type == SDL_MOUSEBUTTONDOWN) ||
                    (event.type == SDL_MOUSEBUTTONDBL))
                        continue;
            }

            // Adjust key modifiers
            if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
                if (gameMode)
                    // All keys go to game
                    storeToGame = 1;
                else {
                    // Adjust numeric keypad
                    if ((event.key.keysym.sym >= SDLK_KP0) && (event.key.keysym.sym <= SDLK_KP9)) {
                        if (event.key.keysym.mod & KMOD_NUM) {
                            event.key.keysym.sym = (SDLKey)(event.key.keysym.sym - SDLK_KP0 + SDLK_0);
                            event.key.keysym.unicode = event.key.keysym.sym;
                        }
                        else {
                            event.key.keysym.sym = keypadConvert[event.key.keysym.sym - SDLK_KP0];
                        }
                    }
                    else if (event.key.keysym.sym == SDLK_KP_PERIOD) {
                        if (event.key.keysym.mod & KMOD_NUM) {
                            event.key.keysym.sym = SDLK_PERIOD;
                            event.key.keysym.unicode = '.';
                        }
                        else {
                            event.key.keysym.sym = SDLK_DELETE;
                        }
                    }
                    SDLMod mod = KMOD_NONE;
                    if (event.key.keysym.mod & KMOD_ALT) mod = (SDLMod)(mod | KMOD_ALT);
                    if (event.key.keysym.mod & KMOD_CTRL) mod = (SDLMod)(mod | KMOD_CTRL);
                    if (event.key.keysym.mod & KMOD_SHIFT) mod = (SDLMod)(mod | KMOD_SHIFT);
                    event.key.keysym.mod = mod;

                    // Do "command release"
                    if ((event.type == SDL_KEYUP) && (event.key.keysym.sym == lastShortcutKey) && (lastShortcutKey)) {
                        lastShortcutKey = 0;
                        customEvent.type = SDL_COMMAND;
                        customEvent.user.code = CMD_RELEASE;
                        handleEvent(&customEvent);
                        customEvent.type = SDL_SPECIAL;
                    }
                }
            }
            
            // Adjust for double-click
            else if (event.type == SDL_MOUSEBUTTONDOWN) {
                if (event.button.button == SDL_BUTTON_LEFT) {
                    if ((dclickTicks) &&
                        ((SDL_GetTicks() - dclickTicks) < DOUBLECLICK_TIME) &&
                        (abs(dclickX - event.button.x) <= DOUBLECLICK_DIST) &&
                        (abs(dclickY - event.button.y) <= DOUBLECLICK_DIST)) {
                        event.type = SDL_MOUSEBUTTONDBL;
                        dclickTicks = 0;
                    }
                    else {
                        dclickX = event.button.x;
                        dclickY = event.button.y;
                        dclickTicks = SDL_GetTicks();
                    }
                }
                else {
                    // (cancel upon any non-left click)
                    dclickTicks = 0;
                }
            }

            // Special handling for QUIT
            else if ((event.type == SDL_QUIT) || ((event.type == SDL_SPECIAL) && (event.user.code & SDL_SPECIALQUIT))) {
                // Can't quit with modal dialogs open
                if (modalPos) continue;
            
                // Skip if we're already doing a prequit- that means we have
                // a modal "save" dialog or something open already; this should be
                // covered by the above condition, but "just in case"
                if (preQuit) continue;
            
                // First, run a pre-quit event
                customEvent.user.code = SDL_PREQUIT;
                
                preQuit = 1;
                handleEvent(&customEvent);
                preQuit = 0;
                
                // Cancel the quit?
                if (cancelPreQuit) {
                    cancelPreQuit = 0;
                    continue;
                }
            
                if (event.type == SDL_QUIT) quit = 1;
                else quit = event.user.code;
            }
            
            // Turn resize events into a resolution change
            else if (event.type == SDL_VIDEORESIZE) {
                // Catch non-fatal video errors
                try {
                    selectVideoMode(event.resize.w, event.resize.h, -1, 0, 1);
                }
                catch (const VideoException& e) {
                    guiErrorBox(string(e.details), errorTitleVideo);
                }
                continue;
            }

            // Turn active events into gain/lose mouse/input focus
            else if (event.type == SDL_ACTIVEEVENT) {
                if ((event.active.state & SDL_APPMOUSEFOCUS) && (event.active.gain == 0) && (currentMouseFocus)) {
                    focusEvent(SDL_MOUSEFOCUS, 0, currentMouseFocus);
                    currentMouseFocus = NULL;
                    selectMouse(MOUSE_NORMAL);
                }
                if ((event.active.state & SDL_APPINPUTFOCUS) && (event.active.gain == 0)) {
                    if (!lastFocus) {
                        if (previousFocus) lastFocus = previousFocus;
                        else lastFocus = currentFocus;
                        changeInputFocus(NULL);
                    }
                }
                if ((event.active.state & SDL_APPINPUTFOCUS) && (event.active.gain == 1)) {
                    // Fixes "stuck ALT" etc
                    // @TODO: SDL_ResetKeyboard();
                    changeInputFocus(lastFocus);
                    lastFocus = NULL;
                    // Ignore the mouse click that generated this (hopefully)
                    initEventCleanup();
                }
            }
    
            if (!storeToGame) handleEvent(&event);
            
            // Tooltips- record time so idle can make them appear
            // Also, COPY all mouse events to game
            if ((event.type == SDL_MOUSEBUTTONDOWN) ||
                (event.type == SDL_MOUSEBUTTONDBL) ||
                (event.type == SDL_MOUSEBUTTONUP) ||
                (event.type == SDL_MOUSEMOTION)) {
                tooltipMs = SDL_GetTicks();
                storeToGame = 1;
            }
            else if ((event.type == SDL_KEYDOWN) ||
                     (event.type == SDL_KEYUP) ||
                     (event.type == SDL_ACTIVEEVENT)) {
                tooltipMs = 0;
            }
            
            // Also copy all ACTIVE and QUIT events to game
            if ((event.type == SDL_ACTIVEEVENT) ||
                (event.type == SDL_QUIT)) {
                storeToGame = 1;
            }
            
/* SDL_OBJECTCHANGE events no longer use the queue
**
            // Object change events- delete structure
            else if (event.type == SDL_OBJECTCHANGE) {
                delete (ObjChange*)event.user.data1;
            }
**
*/
            if ((storeToGame) && (gameMode)) storeEventGame(&event);
        }
        else {
            int phaseChange = 0;
            eventCleanup = 0;

            // Update selection rectangle phase        
            Uint32 ticks = SDL_GetTicks();
            // (catch wraparound)
            if (ticks < selectionPhaseMs) selectionPhaseMs = SDL_GetTicks();
            else if (ticks - selectionPhaseMs > DELAY_PHASE_ROTATE) {
                cursorPhase = (cursorPhase + 1) % 64;
                int grey = abs(32 - cursorPhase) << 3;
                if (grey > 255) grey = 255;
                guiRGB[COLOR_TILECURSOR].r = grey;
                guiRGB[COLOR_TILECURSOR].g = grey;
                guiRGB[COLOR_TILECURSOR].b = grey;
                cursorAlpha = abs(16 - cursorPhase % 32) << 4;
                if (cursorAlpha < 64) cursorAlpha = 64;
                if (cursorAlpha > 255) cursorAlpha = 255;
                guiPacked[COLOR_TILECURSOR] = convertRGB(guiRGB[COLOR_TILECURSOR]);

                phaseChange = 1;
                
                if (cursorPhase % 4 == 0) {
                    selectionPhase = (selectionPhase + 1) % NUM_PHASES;
                    phaseChange = 2;
                }

                selectionPhaseMs = SDL_GetTicks();
            }

            // Display tooltip?
            if ((tooltipMs) && (!tooltip) && (currentMouseFocus) &&
                ((SDL_GetMouseState(NULL, NULL) & (SDL_BUTTON_LMASK | SDL_BUTTON_RMASK)) == 0)) {
                // (catch wraparound)
                if (ticks < tooltipMs) tooltipMs = SDL_GetTicks();
                else if (ticks - tooltipMs > DELAY_TOOLTIP_SHOW) {
                    tooltipMs = 0;
                    const char* tiptext = currentMouseFocus->tooltip(lastMouseX - currentMouseFocus->getX(), lastMouseY - currentMouseFocus->getY());
                    if (tiptext) {
                        tooltip = new ToolTip(tiptext);
                        tooltip->popup(lastMouseX, lastMouseY + mouseTooltipYOffset());
                    }
                }
            }

            // Idle event
            customEvent.user.code = phaseChange == 2 ? SDL_IDLEPHASE : phaseChange ? SDL_IDLECURSOR : SDL_IDLE;

            // Longer idle timeouts
            ++idleTimeout;
            if (idleTimeout > DELAY_IDLE_LONG) {
                customEvent.user.code = SDL_IDLETIMEOUTLONG;
                idleTimeout = 0;
            }
            else if ((!didShortTimeout) && (idleTimeout > DELAY_IDLE_SHORT)) {
                customEvent.user.code = SDL_IDLETIMEOUTSHORT;
                didShortTimeout = 1;
            }

            handleEvent(&customEvent);
            // @TODO: skip if calling game loop, have game loop delay as usual?
            SDL_Delay(1);
        }

        // Skip screen update if focus/close events waiting
        if (eventsWaiting()) continue;
        
        updateScreen();
        
        // If a modal loop, check if our modal dialog has closed- that means we're done
        if (modalLoopPos) {
            if (!verifyWindow(modalLoopPos)) break;
            // Check that our modal window is on top, or a window allowed to sort above it
            // ("no window" is also allowed, because that occurs when you switch to another app)
            if ((modalLoopPos != currentFocus) && (currentFocus) && (currentFocus->windowSort() <= Window::WINDOWSORT_MODAL)) {
                changeInputFocus(modalLoopPos);
            }
        }
    }
    
    --eventLoopCount;
    
    // Return value
    int returnValue = 0;

    // End quit state if all loops have ended
    if (eventLoopCount == 0) {
        returnValue = quit;
        quit = 0;
    }
    
    // Return modal value?
    if (modalReturn) {
        // (We do have to reset it to zero again)
        returnValue = modalReturn;
        modalReturn = 0;
    }
    
    return returnValue;
}

void Desktop::closeAllWindows() { start_func
    SDL_Event customEvent;
    customEvent.type = SDL_CLOSE;
    customEvent.user.code = 0;
    customEvent.user.data1 = NULL;
    customEvent.user.data2 = NULL;

    list<Window*>::iterator pos;

    // Reverse order
    for (pos = windowDataFocus.begin(); pos != windowDataFocus.end(); pos = windowDataFocus.begin()) {
        assert(*pos);
        Window* ptr = *pos;

        windowRunEvent(ptr, &customEvent);
        // In case it hasn't removed itself
        removeWindow(ptr);
        if (ptr->wantsToBeDeleted()) {
            ptr->setParent(NULL);
            delete ptr;
        }
    }
}

void Desktop::deleteAllWindows() { start_func
    currentFocus = NULL;
    previousFocus = NULL;
    lastFocus = NULL;
    currentMouseFocus = NULL;
    modalPos = NULL;

    // Note that we purposely use windowDataFocus here
    // and windowDataDisplay in removeWindow
    windowDataDisplay.clear();
    list<Window*>::reverse_iterator pos;
    list<Window*>::reverse_iterator next;
    list<Window*>::reverse_iterator end = windowDataFocus.rend();

    // Reverse order
    for (pos = windowDataFocus.rbegin(); pos != end; pos = next) {
        assert(*pos);

        // Note the next element in sequence now, before we delete
        next = pos;
        ++next;

        // Delete- removeWindow, if called, shouldn't do anything
        (*pos)->setParent(NULL);
        if ((*pos)->wantsToBeDeleted()) delete *pos;
    }
    
    windowDataFocus.clear();
}

void Desktop::broadcastObjChange(int code, void* object, int data1, int data2, Window* exempt) { start_func
    ObjChange obj;
    obj.obj = object;
    obj.info1 = data1;
    obj.info2 = data2;

    SDL_Event customEvent;
    customEvent.type = SDL_OBJECTCHANGE;
    customEvent.user.code = code;
    customEvent.user.data1 = &obj;
    customEvent.user.data2 = exempt;
/* SDL_OBJECTCHANGE events no longer use the queue
**
    // In order to use this, obj (above) needs to be dynamically allocated again
    // combinatoryEvent(&customEvent);
**
*/
    handleEvent(&customEvent);
}

int Desktop::broadcastEvent(int type, int code, void* data, Window* exempt, int runImmediate) { start_func
    assert((type == SDL_COMMAND) || (type == SDL_SPECIAL) || (type == SDL_SYSKEY) ||
           (type == SDL_QUIT));
    assert(type < SDL_NUMEVENTS);

    SDL_Event customEvent;
    customEvent.type = type;
    customEvent.user.code = code;
    customEvent.user.data1 = data;
    customEvent.user.data2 = exempt;
    if (runImmediate) {
        return handleEvent(&customEvent);
    }
    else if (SDL_PushEvent(&customEvent)) {
        fatalCrash(0, "SDL_PushEvent failure");
    }
    return 0;
}

void Desktop::focusEvent(int type, int code, Window* window) { start_func
    if (!window) return;
    
    assert((type == SDL_MOUSEFOCUS) || (type == SDL_CLOSE) || (type == SDL_INPUTFOCUS));
    assert((code == 0) || (code == 1));

    SDL_Event customEvent;
    customEvent.type = type;
    customEvent.user.code = code;
    customEvent.user.data1 = window;
    customEvent.user.data2 = NULL;
    if (type == SDL_CLOSE) preemptEvent(&customEvent);
    else insertEvent(&customEvent);
}

void Desktop::setModalReturn(int returnValue) { start_func
    if (modalPos) modalReturn = returnValue;
}

void Desktop::cancelQuit() { start_func
    if (preQuit) cancelPreQuit = 1;
}

int Desktop::cancelQuitState() { start_func
    return cancelPreQuit;
}

void Desktop::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    overallWidth = screenWidth;
    overallHeight = screenHeight;
    setBackground(backgroundImage, backgroundColorIndex);

    WindowCollection::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
}

void Desktop::setChildDirty() { start_func
    childDirty = 1;
}

void Desktop::setBackgroundAlpha(int alpha) { start_func
    if (alpha > 255) alpha = 255;
    if (alpha < 0) alpha = 0;
    backgroundAlpha = alpha;

    updateRect.x = 0;
    updateRect.y = 0;
    updateRect.w = overallWidth;
    updateRect.h = overallHeight;
}

void Desktop::setBackground(SDL_Surface* image, int colorIndex) { start_func
    assert(colorIndex >= 0);
    assert(colorIndex < COLOR_COUNT);

    backgroundColorIndex = colorIndex;
    
    if (image) {
        // Calculate position (centered)
        bkSrcW = image->w;
        bkSrcH = image->h;
        bkSrcX = 0;
        bkSrcY = 0;
        bkDestX = (overallWidth - bkSrcW) / 2;
        bkDestY = (overallHeight - bkSrcH) / 2;
        if (bkDestX < 0) {
            bkSrcW = overallWidth;
            bkSrcX = -bkDestX;
            bkDestX = 0;
        }
        if (bkDestY < 0) {
            bkSrcH = overallHeight;
            bkSrcY = -bkDestY;
            bkDestY = 0;
        }

        SDL_Surface* convertedBk = SDL_ConvertSurface(image, getScreen()->format, getScreen()->flags);
        if (backgroundImage) SDL_FreeSurface(backgroundImage);
        backgroundImage = convertedBk;
    }
    else {
        if (backgroundImage) SDL_FreeSurface(backgroundImage);
        backgroundImage = NULL;
    }

    updateRect.x = 0;
    updateRect.y = 0;
    updateRect.w = overallWidth;
    updateRect.h = overallHeight;
}

void Desktop::initEventCleanup() { start_func
    eventCleanup = 1;
}

Desktop::Desktop() : WindowCollection() { start_func
    gameLoopH = NULL;
    supportsCommandH = NULL;
    backgroundImage = NULL;
    backgroundColorIndex = COLOR_FRONTDESKTOP;
    backgroundAlpha = 255;

    quit = 0;
    preQuit = 0;
    cancelPreQuit = 0;
    eventLoopCount = 0;
    modalReturn = 0;
    eventCleanup = 0;

    overallWidth = screenWidth;
    overallHeight = screenHeight;
    canvasWidth = screenWidth;
    canvasHeight = screenHeight;
    
    updateRect.x = 0;
    updateRect.y = 0;
    updateRect.w = overallWidth;
    updateRect.h = overallHeight;

    childDirty = 0;
    dclickTicks = 0;

    selectionPhase = 0;
    selectionPhaseMs = SDL_GetTicks();
    cursorPhase = 0;
    cursorAlpha = 128;

    tooltip = NULL;
    tooltipMs = 0;

    lastShortcutKey = 0;
}

Desktop::~Desktop() { start_func
    if (backgroundImage) SDL_FreeSurface(backgroundImage);
}

int Desktop::getScreenX() const { start_func
    return 0;
}

int Desktop::getScreenY() const { start_func
    return 0;
}

int Desktop::desktopX() const { start_func 
    return canvasX; 
}

int Desktop::desktopY() const { start_func 
    return canvasY; 
}

int Desktop::desktopWidth() const { start_func 
    return canvasWidth;
}

int Desktop::desktopHeight() const { start_func 
    return canvasHeight;
}

void Desktop::childMoved(int fromX, int fromY, int toX, int toY, Window* child) { start_func
    assert(child);
    
    Rect moved;
    child->getRect(moved);
    addToUpdateRect(moved);
    moved.x = fromX;
    moved.y = fromY;
    addToUpdateRect(moved);
}

void Desktop::childResized(int fromW, int fromH, int toW, int toH, Window* child) { start_func
    assert(child);
    
    Rect moved;
    child->getRect(moved);
    if (fromW > toW) moved.w = fromW;
    if (fromH > toH) moved.h = fromH;
    addToUpdateRect(moved);
}

void Desktop::childDeleted(Window* child) { start_func
    assert(child);

    // (just in case the window forgot to remove itself from the desktop)
    removeWindow(child);
}

void Desktop::childModified(Window* child) { start_func
    assert(child);
}

void Desktop::siblingModified(Window* sibling) { start_func
    assert(sibling);
}

